namespace ScrumSprintMonitor.ConfigEditor.Views.ConfigurationView
{
	#region #using Directives

	using System;
	using System.Collections.Generic;
	using System.ComponentModel;
	using System.Diagnostics;
	using System.IO;
	using System.Linq;
	using System.Reflection;
	using System.Windows;
	using System.Windows.Input;

	using ContinuousLinq.Reactive;

	using Implementation;
	using Implementation.Configuration;

	using Infrastructure;
	using Infrastructure.Configuration;

	using Microsoft.Practices.Composite.Presentation.Commands;
	using Microsoft.Practices.ServiceLocation;
	using Microsoft.SDK.Samples.VistaBridge.Library;
	using Microsoft.Win32;

	using Ninject.Core;
	using Ninject.Core.Parameters;

	using PresentationConfigurationEditorView;

	using Properties;

	using SprintConfigurationEditorView;

	using UI.Wpf;

	#endregion

	public class ConfigurationViewModel : ReactiveObject,
	                                      IConfigurationViewModel
	{
		private readonly List<string> availableTfsServers = new List<string>();
		private readonly DelegateCommand<Window> browsePresentationConfigurationCommand;
		private readonly DelegateCommand<Window> browseSprintConfigurationCommand;
		private readonly DelegateCommand<Window> browseTeamMembersConfigurationCommand;
		private readonly DelegateCommand<Window> cancelCommand;
		private readonly DelegateCommand<Window> editPresentationConfigurationCommand;
		private readonly DelegateCommand<Window> editSprintConfigurationCommand;
		private readonly DelegateCommand<Window> editTeamMembersConfigurationCommand;
		private readonly DelegateCommand<Window> okCommand;
		private readonly DelegateCommand<Window> viewLogFileCommand;

		static ConfigurationViewModel()
		{
			var dependsOn = Register<ConfigurationViewModel>();

			dependsOn.Call(me => me.OnPropertyChanged("IsConsiderOutlookTentativeAsFreeTimeEnabled")).OnChanged(me => me.EnableOutlookIntegration).OnChanged(
				me => me.IsConfigurationWritable);
		}

		public ConfigurationViewModel()
		{
			this.browsePresentationConfigurationCommand = new DelegateCommand<Window>(BrowsePresentationConfiguration, CanBrowsePresentationConfiguration);
			this.browseSprintConfigurationCommand = new DelegateCommand<Window>(BrowseSprintConfiguration, CanBrowseSprintConfiguration);
			this.browseTeamMembersConfigurationCommand = new DelegateCommand<Window>(BrowseTeamMembersConfiguration, CanBrowseTeamMembersConfiguration);
			this.editPresentationConfigurationCommand = new DelegateCommand<Window>(EditPresentationConfiguration, CanEditPresentationConfiguration);
			this.editSprintConfigurationCommand = new DelegateCommand<Window>(EditSprintConfiguration, CanEditSprintConfiguration);
			this.editTeamMembersConfigurationCommand = new DelegateCommand<Window>(EditTeamMembersConfiguration, CanEditTeamMembersConfiguration);
			this.viewLogFileCommand = new DelegateCommand<Window>(ViewLogFile, CanViewLogFile);
			this.okCommand = new DelegateCommand<Window>(OnOK, CanCloseWithOK);
			this.cancelCommand = new DelegateCommand<Window>(OnCancel, CanCloseWithCancel);

			PopulateTfsServerNamesList();
		}

		protected string HomePath
		{
			get { return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); }
		}

		#region IConfigurationViewModel Members

		public bool IsConfigurationWritable
		{
			get { return !Settings.Default.IsReadOnly; }
		}

		public IList<string> AvailableTfsServers
		{
			get { return this.availableTfsServers; }
		}

		public string TfsServerName
		{
			get { return Settings.Default.TfsServerName; }
			set
			{
				if (value != Settings.Default.TfsServerName)
				{
					Settings.Default.TfsServerName = value;

					OnPropertyChanged("TfsServerName");
				}
			}
		}

		public string ConfigurationFilePath
		{
			get { return ShortenPath(HomePath, Settings.Default.ConfigurationFilePath); }
			set
			{
				string newValue = GetFullPath(value);

				if (newValue != Settings.Default.ConfigurationFilePath)
				{
					Settings.Default.ConfigurationFilePath = newValue;

					OnPropertyChanged("ConfigurationFilePath");

					this.editSprintConfigurationCommand.RaiseCanExecuteChanged();
				}
			}
		}

		public ICommand BrowseSprintConfigurationCommand
		{
			get { return this.browseSprintConfigurationCommand; }
		}

		public ICommand EditSprintConfigurationCommand
		{
			get { return this.editSprintConfigurationCommand; }
		}

		public string TeamMembersConfigurationFilePath
		{
			get { return ShortenPath(HomePath, Settings.Default.TeamMembersConfigurationFilePath); }
			set
			{
				string newValue = GetFullPath(value);

				if (newValue != Settings.Default.TeamMembersConfigurationFilePath)
				{
					Settings.Default.TeamMembersConfigurationFilePath = newValue;

					OnPropertyChanged("TeamMembersConfigurationFilePath");

					this.editTeamMembersConfigurationCommand.RaiseCanExecuteChanged();
				}
			}
		}

		public ICommand BrowseTeamMembersConfigurationCommand
		{
			get { return this.browseTeamMembersConfigurationCommand; }
		}

		public ICommand EditTeamMembersConfigurationCommand
		{
			get { return this.editTeamMembersConfigurationCommand; }
		}

		public string PresentationConfigurationFilePath
		{
			get { return ShortenPath(HomePath, Settings.Default.PresentationConfigurationFilePath); }
			set
			{
				string newValue = GetFullPath(value);

				if (newValue != Settings.Default.PresentationConfigurationFilePath)
				{
					Settings.Default.PresentationConfigurationFilePath = newValue;

					OnPropertyChanged("PresentationConfigurationFilePath");

					this.editPresentationConfigurationCommand.RaiseCanExecuteChanged();
				}
			}
		}

		public ICommand BrowsePresentationConfigurationCommand
		{
			get { return this.browsePresentationConfigurationCommand; }
		}

		public ICommand EditPresentationConfigurationCommand
		{
			get { return this.editPresentationConfigurationCommand; }
		}

		public bool EnableOutlookIntegration
		{
			get { return Settings.Default.EnableOutlookIntegration; }
			set
			{
				if (value != Settings.Default.EnableOutlookIntegration)
				{
					Settings.Default.EnableOutlookIntegration = value;

					OnPropertyChanged("EnableOutlookIntegration");
				}
			}
		}

		public bool ConsiderOutlookTentativeAsFreeTime
		{
			get { return Settings.Default.ConsiderOutlookTentativeAsFreeTime; }
			set
			{
				if (value != Settings.Default.ConsiderOutlookTentativeAsFreeTime)
				{
					Settings.Default.ConsiderOutlookTentativeAsFreeTime = value;

					OnPropertyChanged("ConsiderOutlookTentativeAsFreeTime");
				}
			}
		}

		public bool IsConsiderOutlookTentativeAsFreeTimeEnabled
		{
			get { return IsConfigurationWritable && EnableOutlookIntegration; }
		}

		public bool ReportUnestimatedSprintBacklogItems
		{
			get { return Settings.Default.ReportUnestimatedSprintBacklogItems; }
			set
			{
				if (value != Settings.Default.ReportUnestimatedSprintBacklogItems)
				{
					Settings.Default.ReportUnestimatedSprintBacklogItems = value;

					OnPropertyChanged("ReportUnestimatedSprintBacklogItems");
				}
			}
		}

		public bool InPlanningMode
		{
			get { return Settings.Default.ActiveSprintMode == SprintMode.Planning; }
			set
			{
				if (value != InPlanningMode)
				{
					Settings.Default.ActiveSprintMode = (value ? SprintMode.Planning : SprintMode.Sprinting);

					OnPropertyChanged("InPlanningMode");
				}
			}
		}

		public ICommand ViewLogFileCommand
		{
			get { return this.viewLogFileCommand; }
		}

		public ICommand OkCommand
		{
			get { return this.okCommand; }
		}

		public ICommand CancelCommand
		{
			get { return this.cancelCommand; }
		}

		public void OnViewActivated()
		{
			this.editSprintConfigurationCommand.RaiseCanExecuteChanged();
			this.editTeamMembersConfigurationCommand.RaiseCanExecuteChanged();
			this.editPresentationConfigurationCommand.RaiseCanExecuteChanged();
		}

		#endregion

		#region Commands

		private static bool CanCloseWithCancel(Window configurationWindow)
		{
			return true;
		}

		private static void OnCancel(Window configurationWindow)
		{
			configurationWindow.DialogResult = false;
			configurationWindow.Close();
		}

		private static bool CanCloseWithOK(Window configurationWindow)
		{
			return true;
		}

		private static void OnOK(Window configurationWindow)
		{
			configurationWindow.DialogResult = true;
			configurationWindow.Close();
		}

		private static bool CanBrowseTeamMembersConfiguration(Window configurationWindow)
		{
			return true;
		}

		private void BrowseTeamMembersConfiguration(Window configurationWindow)
		{
			var usageIdentifierGuid = new Guid("{9697D713-08B8-446f-96E3-C59300AF2BC1}");

			// Configure open file dialog box
			string filePath = GetFullPath(TeamMembersConfigurationFilePath);
			string newFilePath = ShowFileOpenDialog(filePath, configurationWindow, Resources.OpenFileDialog_TeamMembersConfigurationFile_Title,
			                                        Resources.OpenFileDialog_TeamMembersConfigurationFile_Filter, ".xml", "team-info.xml", usageIdentifierGuid);

			// Process open file dialog box results
			if (newFilePath != null)
			{
				// Open document
				TeamMembersConfigurationFilePath = newFilePath;
			}
		}

		private static bool CanBrowsePresentationConfiguration(Window configurationWindow)
		{
			return true;
		}

		private void BrowsePresentationConfiguration(Window configurationWindow)
		{
			var usageIdentifierGuid = new Guid("{0AA5C971-8539-480e-B009-7F8ECA8C9A75}");

			// Configure open file dialog box
			string filePath = GetFullPath(PresentationConfigurationFilePath);
			string newFilePath = ShowFileOpenDialog(filePath, configurationWindow, Resources.OpenFileDialog_PresentationConfigurationFile_Title,
			                                        Resources.OpenFileDialog_PresentationConfigurationFile_Filter, ".txt", "PresentationConfiguration.txt",
			                                        usageIdentifierGuid);

			// Process open file dialog box results
			if (newFilePath != null)
			{
				// Open document
				PresentationConfigurationFilePath = newFilePath;
			}
		}

		private static bool CanBrowseSprintConfiguration(Window configurationWindow)
		{
			return true;
		}

		private void BrowseSprintConfiguration(Window configurationWindow)
		{
			var usageIdentifierGuid = new Guid("{40AA5246-11CD-4dd7-9B55-54B49CF98503}");

			// Configure open file dialog box
			string filePath = GetFullPath(ConfigurationFilePath);
			string newFilePath = ShowFileOpenDialog(filePath, configurationWindow, Resources.OpenFileDialog_ConfigurationFile_Title,
			                                        Resources.OpenFileDialog_ConfigurationFile_Filter, ".txt", "SprintConfiguration.txt", usageIdentifierGuid);

			// Process open file dialog box results
			if (newFilePath != null)
			{
				// Open document
				ConfigurationFilePath = newFilePath;

				CheckIfNeedToMoveToNewFolder(configurationWindow, newFilePath);
			}
		}

		private bool CanEditTeamMembersConfiguration(Window configurationWindow)
		{
			return File.Exists(GetFullPath(TeamMembersConfigurationFilePath));
		}

		private void EditTeamMembersConfiguration(Window configurationWindow)
		{
			const string excelFileName = "excel.exe";

			try
			{
				configurationWindow.Cursor = Cursors.Wait;
				try
				{
					var excelProcess = Process.Start(excelFileName, string.Format("\"{0}\"", GetFullPath(TeamMembersConfigurationFilePath)));
					if (excelProcess != null)
					{
						excelProcess.WaitForInputIdle(10000);
					}
				}
				finally
				{
					configurationWindow.Cursor = null;
				}
			}
			catch (Win32Exception ex)
			{
				if (ex.NativeErrorCode == 2)
				{
					MessageBox.Show(string.Format("Could not find '{0}'. Do you have Microsoft Excel 2007 installed?", excelFileName),
					                Application.Current.MainWindow.Title);
				}
				else
				{
					MessageBox.Show(ex.Message, Application.Current.MainWindow.Title);
				}
			}
		}

		private bool CanEditPresentationConfiguration(Window configurationWindow)
		{
			return File.Exists(GetFullPath(PresentationConfigurationFilePath));
		}

		private static void EditPresentationConfiguration(Window configurationWindow)
		{
			configurationWindow.Cursor = Cursors.Wait;
			try
			{
				var kernel = ServiceLocator.Current.GetInstance<IKernel>();
				var configuration =
					kernel.Get<StreamBasedPresentationConfiguration>(With.Parameters.ConstructorArgument("streamProvider",
					                                                                                     kernel.Get<FileDataStreamProvider>(
					                                                                                     	With.Parameters.ConstructorArgument("filePath",
					                                                                                     	                                    Settings.Default.
					                                                                                     	                                    	PresentationConfigurationFilePath))));
				var model = kernel.Get<IPresentationConfigurationEditorViewModel>(With.Parameters.ConstructorArgument("configuration", configuration));
				var window = new PresentationConfigurationEditorWindow(model)
				             	{
				             		Owner = configurationWindow
				             	};
				window.ShowDialog();
			}
			catch (Exception ex)
			{
				ServiceLocator.Current.GetInstance<ILogger<ConfigurationViewModel>>().Error(ex, "Exception found while opening presentation configuration editor.");

				MessageBox.Show(ex.Message, Application.Current.MainWindow.Title);
			}
			finally
			{
				configurationWindow.Cursor = null;
			}
		}

		private bool CanEditSprintConfiguration(Window configurationWindow)
		{
			return File.Exists(GetFullPath(ConfigurationFilePath));
		}

		private static void EditSprintConfiguration(Window configurationWindow)
		{
			configurationWindow.Cursor = Cursors.Wait;
			try
			{
				var kernel = ServiceLocator.Current.GetInstance<IKernel>();
				var configuration =
					kernel.Get<StreamBasedSprintConfiguration>(With.Parameters.ConstructorArgument("streamProvider",
					                                                                               kernel.Get<FileDataStreamProvider>(
					                                                                               	With.Parameters.ConstructorArgument("filePath",
					                                                                               	                                    Settings.Default.
					                                                                               	                                    	ConfigurationFilePath))));
				var parameters = new Dictionary<string, object>
				                 	{
				                 		{"configuration", configuration}
				                 	};
				var model = kernel.Get<ISprintConfigurationEditorViewModel>(With.Parameters.ConstructorArguments(parameters));
				var window = new SprintConfigurationEditorWindow(model)
				             	{
				             		Owner = configurationWindow
				             	};
				window.ShowDialog();
			}
			catch (Exception ex)
			{
				ServiceLocator.Current.GetInstance<ILogger<ConfigurationViewModel>>().Error(ex, "Exception found while opening sprint configuration editor.");

				MessageBox.Show(ex.Message, Application.Current.MainWindow.Title);
			}
			finally
			{
				configurationWindow.Cursor = null;
			}
		}

		private static bool CanViewLogFile(Window configurationWindow)
		{
			return true;
		}

		private static void ViewLogFile(Window configurationWindow)
		{
			try
			{
				configurationWindow.Cursor = Cursors.Wait;
				try
				{
					const string logFileBaseName = "scrumsprintmonitor.log";
					string assemblyLocation = Assembly.GetEntryAssembly().Location;
					var versionInfo = FileVersionInfo.GetVersionInfo(assemblyLocation);
					string appDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
					                                    versionInfo.CompanyName.Replace(' ', '_'));
					string logFileName = Path.Combine(appDataFolder, logFileBaseName);

					var process = Process.Start(logFileName);
					if (process != null)
					{
						process.WaitForInputIdle(10000);
					}
				}
				finally
				{
					configurationWindow.Cursor = null;
				}
			}
			catch (Win32Exception ex)
			{
				MessageBox.Show(ex.Message, Application.Current.MainWindow.Title);
			}
		}

		#endregion

		#region Private Methods

		private string GetFullPath(string path)
		{
			return Path.GetFullPath(Path.Combine(HomePath, path));
		}

		private void PopulateTfsServerNamesList()
		{
			for (int version = 8; version <= 15; ++version)
			{
				string keyPath = string.Format(@"Software\Microsoft\VisualStudio\{0}.0\TeamFoundation\{1}", version, (version >= 10 ? "Instances" : "Servers"));

				using (var key = Registry.CurrentUser.OpenSubKey(keyPath, false))
				{
					if (key != null)
					{
						this.availableTfsServers.AddRange(version >= 10 ? key.GetSubKeyNames() : key.GetValueNames());
						break;
					}
				}
			}

			if (!string.IsNullOrEmpty(TfsServerName) && !this.availableTfsServers.Contains(TfsServerName, StringComparer.OrdinalIgnoreCase))
			{
				this.availableTfsServers.Add(TfsServerName);
			}

			this.availableTfsServers.Sort();
		}

		private static string ShortenPath(string mainDirPath, string absoluteFilePath)
		{
			string newPath = EvaluateRelativePath(mainDirPath, Path.GetFullPath(absoluteFilePath));

			if (newPath.Length > absoluteFilePath.Length)
			{
				return absoluteFilePath;
			}

			return newPath;
		}

		private static string EvaluateRelativePath(string mainDirPath, string absoluteFilePath)
		{
			if (Path.GetPathRoot(mainDirPath) != Path.GetPathRoot(absoluteFilePath))
			{
				return absoluteFilePath;
			}

			var firstPathParts = mainDirPath.Trim(Path.DirectorySeparatorChar).Split(Path.DirectorySeparatorChar);
			var secondPathParts = absoluteFilePath.Trim(Path.DirectorySeparatorChar).Split(Path.DirectorySeparatorChar);

			int sameCounter = 0;
			for (int i = 0; i < Math.Min(firstPathParts.Length, secondPathParts.Length); i++)
			{
				if (string.Compare(firstPathParts[i], secondPathParts[i].ToLower(), StringComparison.OrdinalIgnoreCase) != 0)
				{
					break;
				}
				sameCounter++;
			}

			if (sameCounter == 0)
			{
				return absoluteFilePath;
			}

			string newPath = string.Empty;
			for (int i = sameCounter; i < firstPathParts.Length; i++)
			{
				if (i > sameCounter)
				{
					newPath += Path.DirectorySeparatorChar;
				}
				newPath += "..";
			}
			if (newPath.Length == 0)
			{
				newPath = ".";
			}
			for (int i = sameCounter; i < secondPathParts.Length; i++)
			{
				newPath += Path.DirectorySeparatorChar;
				newPath += secondPathParts[i];
			}

			return newPath;
		}

		private static string ShowFileOpenDialog(string filePath, Window configurationWindow, string title, string filter, string defaultExtension,
		                                         string defaultFileName, Guid usageIdentifier)
		{
			if (Environment.OSVersion.Version.Major >= 6)
			{
				var dlg = new CommonOpenFileDialog
				          	{
				          		Title = title,
				          		AddToMruList = true,
				          		AddExtension = true,
				          		DereferenceLinks = true,
				          		UsageIdentifier = usageIdentifier
				          	};

				string initialDirectory = Path.GetDirectoryName(filePath);
				if (Directory.Exists(initialDirectory))
				{
					dlg.InitialDirectory = initialDirectory;
				}
				dlg.Filters.Add(new CommonFileDialogFilter(filter.Split(new[] {'|'})[0], filter.Substring(filter.IndexOf('|') + 1)));

				// Show open file dialog box
				return (dlg.ShowDialog().Canceled ? null : dlg.FileName);
			}
			else
			{
				var dlg = new OpenFileDialog
				          	{
				          		Title = title,
				          		InitialDirectory = Path.GetDirectoryName(filePath),
				          		FileName = (File.Exists(filePath) ? filePath : defaultFileName),
				          		DefaultExt = defaultExtension,
				          		Filter = filter,
				          		AddExtension = true,
				          		DereferenceLinks = true
				          	};

				// Show open file dialog box
				return (dlg.ShowDialog(configurationWindow) == true ? dlg.FileName : null);
			}
		}

		private void CheckIfNeedToMoveToNewFolder(Window configurationWindow, string path)
		{
			string newFolder = Path.GetDirectoryName(path);
			string newSprintConfigurationFilePath = Path.Combine(newFolder, Path.GetFileName(ConfigurationFilePath));
			string newPresentationConfigurationFilePath = Path.Combine(newFolder, Path.GetFileName(PresentationConfigurationFilePath));
			string newTeamMembersConfigurationFilePath = Path.Combine(newFolder, Path.GetFileName(TeamMembersConfigurationFilePath));

			if (Path.GetDirectoryName(PresentationConfigurationFilePath) != newFolder && Path.GetDirectoryName(TeamMembersConfigurationFilePath) != newFolder)
			{
				if (File.Exists(newSprintConfigurationFilePath) && File.Exists(newPresentationConfigurationFilePath) &&
				    File.Exists(newTeamMembersConfigurationFilePath))
				{
					var result = MessageBox.Show(configurationWindow, Resources.Prompt_Question_UseTargetFolderConfigurationFileSet, Resources.ProductName,
					                             MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.Yes);

					if (result == MessageBoxResult.Yes)
					{
						ConfigurationFilePath = newSprintConfigurationFilePath;
						TeamMembersConfigurationFilePath = newTeamMembersConfigurationFilePath;
						PresentationConfigurationFilePath = newPresentationConfigurationFilePath;
					}
				}
			}
		}

		#endregion
	}
}